import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;

//@author A0096724W
public class InputParser {
    private static final String ERROR_NO_DESCRIPTION = "ERROR: NO DESCRIPTION";
    private static final String ERROR_TOO_MANY_QUOTES = "ERROR: TOO MANY \"";
    private static final String ERROR_NO_END_TIME = "ERROR: PLEASE INPUT END TIME";
    private static final String ERROR_NO_START_TIME = "ERROR: PLEASE INPUT START TIME";
    private static final String ERROR_WRONG_TIME_FORMAT = "ERROR: INVALID DATE & TIME FORMAT (%1$s)";
    private static final String ERROR_TWO_START_TIMES = "ERROR: TWO START TIMES";
    private static final String ERROR_TWO_END_TIMES = "ERROR: TWO END TIMES";
    private static final String ERROR_TWO_DUE_BY_TIMES = "ERROR: TWO DUE BY TIMES";
    private static final String ERROR_MIXED_FORMAT = "ERROR: UNABLE TO HAVE DUE BY TIME WITH START OR END TIME";
    private static final String ERROR_START_LATER_THAN_END = "ERROR: START DATE LATER THAN END DATE. from (%1$s) to (%2$s)";
    private static final String ERROR_TOO_MANY_BACK_SLASH = "ERROR: TOO MANY \\";
    private static final String ERROR_INVALID_COMMAND = "ERROR: INVALID COMMAND\n";
    private static final String ERROR_NO_INDEX = "ERROR: NO VALID INDEX FOUND.";
    private static final String ERROR_INVALID_LIST = "ERROR: INVALID LIST TYPE";
    private static final String ERROR_INVALID_INDEX = "ERROR: INVALID INDEX (%1$s)";
    
    private boolean isAdd_ = false;
    private boolean isDelete_ = false;
    private boolean isDisplay_ = false;
    private boolean isUpdate_ = false;
    private boolean isDone_ = false;
    private boolean isSearch_ = false;
    private boolean isUndo_ = false;
    private boolean isCalendar_ = false;
    private boolean isExit_ = false;
    private boolean isClash_ = false;
    
    /*
     * Change timeFrom, timeTo, reminderTime when get timeParser
     * Param for add & update
     */
    private int index_;
    private String description_;
    private Date timeFrom_;
    private Date timeTo_;
    private boolean urgent_;
    private boolean reminder_;
    private Date reminderTime_;
    
    /*
     *Param for update 
     */
    private String listType = "";
    private String urgentForUpdate;
    private String reminderForUpdate;
    private boolean isFrom = false;

    /*
     * Param for delete 
     */
    boolean isDeleteAll_ = false;
    ArrayList<Integer> indiceToDelete = new ArrayList<Integer>(); 

    /*
     * Param for done 
     */
    ArrayList<Integer> indiceToDone = new ArrayList<Integer>();

    /*
     * Param for search 
     */
    boolean isSearch = false;

    /*
     * Param for display 
     */
    private String displayType_;

    private final String SPACE_OFFSET = " ";
    private static String[] listHeader = new String[3];

    InputParser(String currentText) {
	setCommandType(currentText + SPACE_OFFSET);
    }

    private void setCommandType(String currentText) {
	currentText = currentText.toLowerCase();
	setAddMode(currentText);
	setDeleteMode(currentText);
	setDisplayMode(currentText);
	setUpdateMode(currentText);
	setDoneMode(currentText);
	setSearchMode(currentText);
	setUndoMode(currentText);
	setCalendarMode(currentText);
	setExitMode(currentText);
	setClashMode(currentText);
    }


    private void setUpdateMode(String currentText) {
	if (currentText.length() >= 7) {
	    String firstSevenChar = currentText.substring(0, 7);
	    if (firstSevenChar.equals("update ")) {
		isUpdate_ = true;
	    } else {
		isUpdate_ = false;
	    }
	}
    }
    
    private void setClashMode(String currentText) {
	isClash_ = currentText.equals("y ") || currentText.equals("Y ");
    }

    private void setAddMode(String currentText) {
	if (currentText.length() >= 4) {
	    String firstFourChar = currentText.substring(0, 4);
	    if (firstFourChar.equals("add ")) {
		isAdd_ = true;
	    } else {
		isAdd_ = false;
	    }
	}
    }

    private void setDeleteMode(String currentText) {
	if (currentText.length() >= 7) {
	    String firstSevenChar = currentText.substring(0, 7);
	    if (firstSevenChar.equals("delete ")) {
		isDelete_ = true;
	    } else {
		isDelete_ = false;
	    }
	}
    }

    private void setDisplayMode(String currentText) {
	String testString = currentText + " ";
	if (testString.length() >= 8) {
	    String firstEightChar = testString.substring(0, 8);
	    if (firstEightChar.equals("display ")) {
		isDisplay_ = true;
	    } else {
		isDisplay_ = false;
	    }
	}
    }

    private void setDoneMode(String currentText) {
	if (currentText.length() >= 5) {
	    String firstFiveChar = currentText.substring(0, 5);
	    if (firstFiveChar.equals("done ")) {
		isDone_ = true;
	    } else {
		isDone_ = false;
	    }
	}
    }

    private void setSearchMode(String currentText){
	if (currentText.length() >= 7) {
	    String firstSevenChar = currentText.substring(0, 7);
	    if (firstSevenChar.equals("search ")) {
		isSearch_ = true;
	    } else {
		isSearch_ = false;
	    }
	}
    }

    private void setUndoMode(String currentText){
	if (currentText.length() >= 5) {
	    String firstSevenChar = currentText.substring(0, 5);
	    if (firstSevenChar.equals("undo ")) {
		isUndo_ = true;
	    } else {
		isUndo_ = false;
	    }
	}
    }

    private void setCalendarMode(String currentText){
	if (currentText.length() >= 9) {
	    String firstNineChar = currentText.substring(0, 9);
	    if (firstNineChar.equals("calendar ")) {
		isCalendar_ = true;
	    } else {
		isCalendar_ = false;
	    }
	}
    }

    private void setExitMode(String currentText){
	if (currentText.length() >= 5) {
	    String firstFiveChar = currentText.substring(0, 5);
	    if (firstFiveChar.equals("exit ")) {
		isExit_ = true;
	    } else {
		isExit_ = false;
	    }
	}
    }

    public String parseListType(String input) throws Exception {
	String toReplace = "";

	if (input.contains(" done")) {
	    listType = "done";
	    toReplace = "done";
	} else if (input.contains(" events")) {
	    listType = "event";
	    toReplace = " events";
	} else if (input.contains(" misc")) {
	    listType = "misc";
	    toReplace = " misc";
	} else if (input.contains(" deadlines")) {
	    listType = "deadline";
	    toReplace = " deadline";
	} else if (input.contains(" all")) {
	    listType = "all";
	    toReplace = listType;
	}

	return input.replace(toReplace, "");
    }

    public String parseDescription(String input) throws Exception {
	//Append white space so that on split always in 3 parts
	String appendSpace = input + " ";
	String[] testInput = appendSpace.split("\"");
	String withoutDescription = "";
	
	//If split into 3 parts means there's only one section surrounded by quotes
	if (testInput.length == 3) {
	    description_ = testInput[1];
	    withoutDescription = testInput[0] + testInput[2];
	    
	//Only for update there is no need for description
	} else if (!isUpdate_){
	    if (testInput.length == 1) {
		throw new Exception(ERROR_NO_DESCRIPTION);
	    } else {
		throw new Exception(ERROR_TOO_MANY_QUOTES);
	    }
	} else {
	    withoutDescription = input;
	}
	return withoutDescription;
    }

    //KIV
    public String parseTime(String input) throws Exception {
	//Append white space so that on split always in 3 parts
	String[] testInput = (input + " ").split("\\\\");
	int numberOfSplits = testInput.length;
	String reducedInput = null;

	//misc task
	if (numberOfSplits == 1) {
	    timeFrom_ = null;
	    timeTo_ = null;
	    reducedInput = input;

	    //Deadline task
	} else if (numberOfSplits == 3) {
	    String timeFrom = testInput[1];

	    timeFrom_ = TimeParser.parseTime(timeFrom);
	    timeTo_ = null;

	    boolean timeFromIsStartTime = isThatTime(testInput[0], "from");
	    boolean timeFromIsEndTime = isThatTime(testInput[0], "to");

	    ifAddCheckTimeIsValid(timeFromIsStartTime, timeFromIsEndTime);
	    ifUpdateSetArgumentsForFunctions(timeFromIsStartTime,
		    timeFromIsEndTime);
	    checkIfTimesAreValid(timeFrom);
	    reducedInput = testInput[0] + testInput[2];

	    //event task	
	} else if (numberOfSplits == 5) {
	    String timeFrom = testInput[1];
	    timeFrom_ = TimeParser.parseTime(timeFrom);

	    String timeTo = testInput[3];
	    timeTo_ = TimeParser.parseTime(timeTo);

	    boolean timeFromIsStartTime = isThatTime(testInput[0], "from");
	    boolean timeToIsStartTime = isThatTime(testInput[2], "from");
	    boolean timeFromIsEndTime = isThatTime(testInput[0], "to");
	    boolean timeToIsEndTime = isThatTime(testInput[2], "to");
	    boolean timeFromIsDueByTime = isThatTime(testInput[0], "due by");
	    boolean timeToIsDueByTime = isThatTime(testInput[0], "due by");

	    checkIfTimesAreValid(timeFromIsStartTime, timeToIsStartTime,
		    timeFromIsEndTime, timeToIsEndTime, timeFromIsDueByTime,
		    timeToIsDueByTime);
	    
	    //If switch the time around
	    if (timeFromIsEndTime && timeToIsStartTime) {
		Date tempDate = timeFrom_;
		timeFrom_ = timeTo_;
		timeTo_ = tempDate;
	    }
	    
	    checkIfTimeHasBeenParsed(timeFrom, timeTo);

	    reducedInput = testInput[0] + testInput[2] + testInput[4];

	} else {
	    throw new Exception (ERROR_TOO_MANY_BACK_SLASH);
	}
	return reducedInput;
    }

    private void checkIfTimeHasBeenParsed(String timeFrom, String timeTo)
	    throws Exception {
	if (timeFrom_ == null) {
	String warning = String.format(ERROR_WRONG_TIME_FORMAT, timeFrom);
	throw new Exception (warning);
	}

	if (timeTo_ == null) {
	String warning = String.format(ERROR_WRONG_TIME_FORMAT, timeTo);
	throw new Exception (warning); 
	}

	if (timeFrom_.after(timeTo_)) {
	String warning = String.format(ERROR_START_LATER_THAN_END, timeFrom, timeTo);
	throw new Exception (warning); 
	}
    }

    private void checkIfTimesAreValid(boolean timeFromIsStartTime,
	    boolean timeToIsStartTime, boolean timeFromIsEndTime,
	    boolean timeToIsEndTime, boolean timeFromIsDueByTime,
	    boolean timeToIsDueByTime) throws Exception {
	if (timeFromIsStartTime && timeToIsStartTime) {
	throw new Exception (ERROR_TWO_START_TIMES);
	}

	if (timeFromIsEndTime && timeToIsEndTime) {
	throw new Exception (ERROR_TWO_END_TIMES);
	}

	if (timeFromIsDueByTime && timeToIsDueByTime) {
	throw new Exception (ERROR_TWO_DUE_BY_TIMES);
	}

	if (timeFromIsDueByTime || timeToIsDueByTime) {
	throw new Exception (ERROR_MIXED_FORMAT);
	}
    }

    private void checkIfTimesAreValid(String timeFrom) throws Exception {
	if (timeFrom_ == null && timeTo_ == null) {
	String warning = String.format(ERROR_WRONG_TIME_FORMAT, timeFrom);
	throw new Exception (warning);
	}
    }

    private void ifUpdateSetArgumentsForFunctions(boolean timeFromIsStartTime,
	    boolean timeFromIsEndTime) {
	if (isUpdate_) {
	if (timeFromIsEndTime) {
	    timeTo_ = timeFrom_;
	    timeFrom_ = null;
	}

	if (timeFromIsStartTime) {
	    isFrom = true;
	}
	}
    }

    private void ifAddCheckTimeIsValid(boolean timeFromIsStartTime,
	    boolean timeFromIsEndTime) throws Exception {
	if (isAdd_) {
	if (timeFromIsStartTime) {
	    throw new Exception(ERROR_NO_END_TIME);
	}

	if (timeFromIsEndTime) {
	    throw new Exception(ERROR_NO_START_TIME);
	}
	}
    }
    
    //Checks if the potential time is start or end or due time based on comparison flag provided
    private boolean isThatTime(String input, String comparison) {
	char[] comparisonArray = comparison.toCharArray();
	char[] inputArray = input.toCharArray();
	int comparisonArrayIndex = comparisonArray.length - 1;
	int originalComparisonIndex = comparisonArray.length - 1;

	for (int i = inputArray.length - 1; i >= 0; i--) {
	    char c = inputArray[i];

	    if (!(c == ' ') || comparisonArrayIndex != originalComparisonIndex) {
		if (c == comparisonArray[comparisonArrayIndex]) {
		    if (comparisonArrayIndex == 0) {
			return true;
		    }
		    comparisonArrayIndex--;
		} else {
		    return false;
		}
	    }
	}

	return false;
    }

    public void parseUrgent(String input) {
	if (input != null) {
	    input = input.toLowerCase();
	    boolean offUrgent = input.contains(" offurgent ");
	    
	    //For add, urgent_ is used as whether the task is urgent or not
	    if (isAdd_) {
		urgent_ = input.contains(" urgent ");
	    }
	    
	    //For update, urgent_ is used as a flag for whether urgent is present and 
	    //urgentForUpdate is used to toggle urgency of item
	    if (isUpdate_) {
		if (offUrgent) {
		    urgent_ = true;
		    urgentForUpdate = "false";
		} else {
		    boolean urgent = input.contains(" urgent ");
		    if (urgent) {
			urgentForUpdate = "true";
		    }
		}
	    }
	}
    }

    public String parseReminder(String input) throws Exception {
	String[] testInput = input.split("reminder");
	String reducedInput = null;
	input = input.toLowerCase();
	
	if (input.contains("offreminder")) {
	    reminderForUpdate = "false";
	    return input;
	}
	
	//If no reminder, do nothing
	try {	
	    if (testInput.length == 1) {
		reducedInput = input;
		
		//Else look for reminder time
	    } else if (testInput.length == 2) {
		reducedInput = testInput[0] + searchForTime(testInput[1] + " "); 
	    }
	} catch (Exception e) {
	    throw e;
	}
	
	return reducedInput;
    }

    private String searchForTime(String s) throws Exception{
	char[] input = s.toCharArray();
	String time = "";
	String reducedInput = null;

	int j = 0;
	
	//While only whitespace, ignore char
	while (input[j] == ' ' && j < input.length) {
	    j++;
	}
	
	//If backslash found, possible reminder time found
	if (input[j] == '\\') {
	    for (int i = j + 1; i < input.length; i++) {
		char c = input[i];
		
		//Only if enclosed by another backslash, reminder time found
		if (c == '\\') {
		    String withoutTime = s.substring(i + 1, input.length - 1);
		    reducedInput = withoutTime;
		    break;
		}
		time += c;
	    }
	}

	String reminderTime = time;
	reminderTime_ = TimeParser.parseTime(reminderTime);
	
	//Check if correct
	if (reminderTime_ == null) {
	    String warning = String.format(ERROR_WRONG_TIME_FORMAT, reminderTime);
	    throw new Exception (warning);
	} else {
	    reminder_ = true;

	    if (isUpdate_) {
		reminderForUpdate = "true";
	    }
	}

	return reducedInput;
    }

    public Feedback parse(String input, Functions functions) throws Exception{
	input = input + " ";
	try {
	    if (isAdd_) {
		parseForAdd(input);
		Functions.clearClashTask();
		return functions.addItem(description_, timeFrom_, timeTo_, urgent_, reminder_, reminderTime_);
	    }

	    if (isDelete_) {
		parseForDelete(input);
		if (listType.equals("done") && isDeleteAll_) {
		    Functions.clearClashTask();
		    return functions.deleteAllDone();
		}
		return functions.deleteAll(listType, indiceToDelete);
	    }

	    if (isDisplay_) {
		parseForDisplay(input);
		if (displayType_.equals("all")) { 
		    Functions.clearClashTask();
		    return functions.displayAll();
		} else {
		    Functions.clearClashTask();
		    return functions.displayByType(displayType_);
		}
	    }

	    if (isUpdate_) {
		parseForUpdate(input);
		Functions.clearClashTask();
		return functions.updateItem(listType, index_, description_, timeFrom_, timeTo_, urgentForUpdate, reminderForUpdate, reminderTime_, isFrom);
	    }

	    if (isDone_) {
		parseForDone(input);
		Functions.clearClashTask();
		return functions.doneMultiple(listType, indiceToDelete);
	    }

	    if (isSearch_){
		System.out.println(parseForSearch(input));
		Functions.clearClashTask();
		return functions.search(parseForSearch(input));
	    }

	    if (isUndo_){
		Functions.clearClashTask();
		return functions.undo();
	    }

	    if (isClash_) {
		return functions.afterClash();
	    }

	    if (isCalendar_) {
		Functions.clearClashTask();
		Calendar cal = parseForCalendar(input);
		return functions.displayCalendarTable(cal);
	    }

	    if (isExit_) {
		Functions.exit();
	    }

	    return new Feedback(-1, ERROR_INVALID_COMMAND, null, listHeader);
	} catch (Exception e) {
	    throw e;
	}
    }

    private Calendar parseForCalendar(String input) throws Exception {
	String time = input.substring(9);
	searchForTime(time + " ");
	Calendar cal = Calendar.getInstance();
	cal.setTime(reminderTime_);

	return cal;
    }

    private String parseForSearch(String input){
	String[] splitInput = input.split(" ", 2);
	return splitInput[1].trim();
    }

    private void parseForDone(String input) throws Exception {
	parseListType(input);
	parseForDelete(input);
	indiceToDone = indiceToDelete;
    }

    private void parseForUpdate(String input) throws Exception {
	String[] splitInput = input.split(" ");
	int index = -1;
	String feedback;
	
	
	index = lookForIndex(splitInput, index);
	
	//If valid index parse for update using add
	if (index == -1) {
	    throw new Exception(ERROR_NO_INDEX);
	} else {
	    try {
		index_ = index;
		feedback = parseForAdd(input + " ");
		parseListType(feedback);
	    } catch (Exception e) {
		throw e;
	    }
	}
    }

    private int lookForIndex(String[] splitInput, int index) {
	for (int i = 1; i < splitInput.length; i++) {
	    String s = splitInput[i];
	    if (!s.equals("")) {
		if (isNumeric(s)) {
		    index = Integer.parseInt(s);
		}
	    }
	}
	return index;
    }

    private void parseForDisplay (String input) throws Exception {
	boolean isDisplayAll = input.replaceAll(" ", "").length() == 7;

	if (isDisplayAll) {
	    displayType_ = "all";
	} else {
	    parseForDisplayType(input);
	}
    }

    private void parseForDisplayType(String input) throws Exception {
	if (input.contains(" done ")) {
	    displayType_ = "done";
	} else if (input.contains(" events ")) {
	    displayType_ = "event";
	} else if (input.contains(" misc ")) {
	    displayType_ = "misc";
	} else if (input.contains(" deadlines ")) {
	    displayType_ = "deadline";
	} else if (input.contains(" today ")) {
	    displayType_ = "today";
	} else {
	    throw new Exception(ERROR_INVALID_LIST);
	}
    }

    private void parseForDelete(String input) throws Exception {
	String newInput = parseListType(input);
	if (newInput.contains(" all")) {
	    isDeleteAll_ = true;
	    newInput = newInput.replace("all", "");
	}
	String[] splitBySpace = newInput.split(" ");

	//Search for a series of indices
	for (int i = 1; i < splitBySpace.length; i++) {
	    String potentialNumber = splitBySpace[i];	

	    if (!potentialNumber.equals("")) {
		if (isNumeric(potentialNumber)) {
		    int index = Integer.parseInt(potentialNumber);

		    if (!indiceToDelete.contains(index)) {
			indiceToDelete.add(index);
		    }
		} else {
		    String warning = String.format(ERROR_INVALID_INDEX, potentialNumber);
		    throw new Exception(warning);
		}
	    }

	}
    }

    //parseForAdd parses for different parts and removes them from the input
    private String parseForAdd(String input) throws Exception {
	try {
	    String inputted = parseDescription(input);
	    inputted = parseReminder(inputted);
	    inputted = parseTime(inputted);
	    parseUrgent(inputted);

	    return inputted;
	} catch (Exception e) {
	    throw e;
	}
    }

    //match a number with optional '-' and decimal.
    public static boolean isNumeric(String str) {
	if (str == null) {
	    return false;
	}

	boolean isNum = str.matches("-?\\d+(\\.\\d+)?"); 

	if (isNum) {
	    int i = Integer.parseInt(str);

	    if (i >= 1) {
		return true;
	    }
	}

	return false;
    }

    public String getListType() {
	return listType;
    }

}
